package com.lagou.ipersistence.reflection;

import com.lagou.ipersistence.reflection.invoker.GetFieldInvoker;
import com.lagou.ipersistence.reflection.invoker.Invoker;
import com.lagou.ipersistence.reflection.invoker.MethodInvoker;
import com.lagou.ipersistence.reflection.invoker.SetFieldInvoker;
import com.lagou.ipersistence.reflection.property.PropertyNamer;

import java.lang.reflect.*;
import java.util.Collection;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/**
 * @author Yan Jiahong
 * Created on 2020/11/16
 */
public class Reflector {
    private final Class<?> type;
    
    private final String[] readablePropertyNames;
    
    private final String[] writablePropertyNames;
    
    /**
     * 其中的key为属性名
     */
    private final Map<String, Invoker> setMethods = new HashMap<>();
    
    private final Map<String, Invoker> getMethods = new HashMap<>();
    
    private final Map<String, Class<?>> setTypes = new HashMap<>();
    
    private final Map<String, Class<?>> getTypes = new HashMap<>();
    
    private Constructor<?> defaultConstructor;
    
    private final Map<String, String> caseInsensitivePropertyNames = new HashMap<>();
    
    public Reflector(Class<?> clazz) {
        this.type = clazz;
        addDefaultConstructor(clazz);
        // 解析Getter方法
        addGetMethods(clazz);
        // 解析Setter方法
        addSetMethods(clazz);
        // 解析那些有字段但是没有getter或者setter方法的字段
        addFields(clazz);
        // 有些字段是final修饰的, 可以读取但是不能写
        readablePropertyNames = getMethods.keySet().toArray(new String[]{});
        writablePropertyNames = setMethods.keySet().toArray(new String[]{});
        for (String propName : readablePropertyNames) {
            caseInsensitivePropertyNames.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
        for (String propName : writablePropertyNames) {
            caseInsensitivePropertyNames.put(propName.toUpperCase(Locale.ENGLISH), propName);
        }
    }
    
    private void addFields(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            if (canControlMemberAccessible()) {
                try {
                    field.setAccessible(true);
                } catch (Exception e) {
                    // do nothing
                }
                if (field.isAccessible()) {
//                    if (!setMethods.containsKey(field.getName())) {
                    int modifiers = field.getModifiers();
                    if (!(Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers))) {
                        addSetField(field);
                    }
//                    }
//                    if (!getMethods.containsKey(field.getName())) {
                    addGetField(field);
//                    }
                }
            }
        }
    }
    
    private void addGetField(Field field) {
        if (isValidPropertyName(field.getName())) {
            getMethods.put(field.getName(), new GetFieldInvoker(field));
            getTypes.put(field.getName(), field.getType());
        }
    }
    
    private void addSetField(Field field) {
        if (isValidPropertyName(field.getName())) {
            setMethods.put(field.getName(), new SetFieldInvoker(field));
            Class<?> type = field.getType();
            setTypes.put(field.getName(), type);
        }
    }
    
    private void addSetMethods(Class<?> clazz) {
        Method[] methods = getClassMethods(clazz);
        for (Method method : methods) {
            String name = method.getName();
            if (method.getParameterTypes().length == 0 || !name.startsWith("set") || !(name.length() > 3)) {
                continue;
            }
            name = PropertyNamer.methodToProperty(name);
            addSetMethod(name, method);
        }
    }
    
    private void addSetMethod(String name, Method method) {
        if (isValidPropertyName(name)) {
            setMethods.put(name, new MethodInvoker(method));
            Class<?>[] parameterTypes = method.getParameterTypes();
            setTypes.put(name, parameterTypes[0]);
        }
    }
    
    
    private void addGetMethods(Class<?> clazz) {
        Method[] methods = getClassMethods(clazz);
        for (Method method : methods) {
            if (method.getParameterTypes().length > 0) {
                continue;
            }
            String name = method.getName();
            if (name.startsWith("get") && name.length() > 3 || name.startsWith("is") && name.length() > 2) {
                name = PropertyNamer.methodToProperty(name);
            }
            addGetterMethod(name, method);
        }
    }
    
    private void addGetterMethod(String name, Method method) {
        if (isValidPropertyName(name)) {
            getMethods.put(name, new MethodInvoker(method));
            getTypes.put(name, method.getReturnType());
        }
    }
    
    private boolean isValidPropertyName(String name) {
        return !(name.startsWith("$") || "serialVersionUID".equals(name) || "class".equals(name));
    }
    
    private void addDefaultConstructor(Class<?> clazz) {
        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            if (constructor.getParameterTypes().length == 0) {
                if (canControlMemberAccessible()) {
                    try {
                        constructor.setAccessible(true);
                    } catch (Exception e) {
                        // do nothing
                    }
                    if (constructor.isAccessible()) {
                        this.defaultConstructor = constructor;
                    }
                }
            }
        }
    }
    
    private Method[] getClassMethods(Class<?> clazz) {
        Map<String, Method> uniqueMethods = new HashMap<>();
        Class<?> currentClass = clazz;
        while (currentClass != null && currentClass != Object.class) {
            // 记录当前类自己的方法
            addUniqueMethods(uniqueMethods, clazz.getDeclaredMethods());
            
            // 记录当前类接口的方法
            Class<?>[] interfaces = currentClass.getInterfaces();
            for (Class<?> inf : interfaces) {
                addUniqueMethods(uniqueMethods, inf.getDeclaredMethods());
            }
            
            // 记录父类的方法
            currentClass = currentClass.getSuperclass();
        }
        Collection<Method> methods = uniqueMethods.values();
        return methods.toArray(new Method[]{});
    }
    
    private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
        for (Method currentMethod : methods) {
            if (!currentMethod.isBridge()) {
                String signature = getSignature(currentMethod);
                if (!uniqueMethods.containsKey(signature)) {
                    if (canControlMemberAccessible()) {
                        try {
                            currentMethod.setAccessible(true);
                        } catch (Exception e) {
                            // do nothing
                        }
                    }
                    uniqueMethods.put(signature, currentMethod);
                }
            }
        }
    }
    
    private String getSignature(Method method) {
        StringBuilder sb = new StringBuilder();
        Class<?> returnType = method.getReturnType();
        sb.append(returnType.getName()).append("#");
        sb.append(method.getName());
        Class<?>[] parameters = method.getParameterTypes();
        for (int i = 0; i < parameters.length; i++) {
            if (i == 0) {
                sb.append(":");
            } else {
                sb.append(",");
            }
            sb.append(parameters[i].getName());
        }
        return sb.toString();
    }
    
    /**
     * 检查当前应用程序是否拥有控制成员变量的可访问性
     */
    private static boolean canControlMemberAccessible() {
        try {
            SecurityManager securityManager = System.getSecurityManager();
            if (securityManager != null) {
                // 当为该程序配置了安全管理器, 则需要检查是否拥有'禁止访问检查'这个权限
                // 当没有权限时候会抛出异常
                securityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
            }
        } catch (Exception e) {
            return false;
        }
        // 没有配置安全管理, 可以拥有完全的访问权限
        return true;
    }
    
    public boolean hasGetter(String name) {
        return getMethods.containsKey(name);
    }
    
    public Class<?> getGetterType(String propertyName) {
        Class<?> clazz = getTypes.get(propertyName);
        if (clazz == null) {
            throw new ReflectionException("在类型'" + type + "'没有名为'" + propertyName + "'的getter方法");
        }
        return clazz;
    }
    
    public Class<?> getSetterType(String propertyName) {
        Class<?> clazz = setTypes.get(propertyName);
        if (clazz == null) {
            throw new ReflectionException("在类型'" + type + "'没有名为'" + propertyName + "'的setter方法");
        }
        return clazz;
    }
    
    public Invoker getSetInvoker(String name) {
        return setMethods.get(name);
    }
    
    public Invoker getGetInvoker(String name) {
        return getMethods.get(name);
    }
    
    public Class<?> getType() {
        return type;
    }
    
    public String[] getReadablePropertyNames() {
        return readablePropertyNames;
    }
    
    public String[] getWritablePropertyNames() {
        return writablePropertyNames;
    }
    
    public Map<String, Invoker> getSetMethods() {
        return setMethods;
    }
    
    public Map<String, Invoker> getGetMethods() {
        return getMethods;
    }
    
    public Map<String, Class<?>> getSetTypes() {
        return setTypes;
    }
    
    public Map<String, Class<?>> getGetTypes() {
        return getTypes;
    }
    
    public Constructor<?> getDefaultConstructor() {
        return defaultConstructor;
    }
    
    public Map<String, String> getCaseInsensitivePropertyNames() {
        return caseInsensitivePropertyNames;
    }
}
